﻿using AutoMapper;
using Library.API.Entities;
using Library.API.Filters;
using Library.API.Helpers;
using Library.API.Models;
using Library.API.Repositories.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Library.API.Controllers
{
    [Route("api/authors")]
    [ApiController]
    [Authorize]
    public class AuthorController : Controller
    {
        public IMapper Mapper { get; set; }

        public IRepositoryWrapper RepositoryWrapper { get; set; }

        public AuthorController(IRepositoryWrapper repositoryWrapper, IMapper mapper)
        {
            RepositoryWrapper = repositoryWrapper;
            Mapper = mapper;
        }

        /// <summary>
        /// 使用http服务器端缓存
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        [HttpGet(Name = nameof(GetAuthorsAsync))]
        public async Task<ActionResult<List<AuthorDto>>> GetAuthorsAsync([FromQuery] AuthorResourceParameters parameters)
        {
            PagedList<Author> pagedList;

            string cacheKey =
                   $"authors_page_{parameters.PageNumber}_pageSize_{parameters.PageSize}_{parameters.SortBy}";
            string cacheContent = await RedisHelper.GetAsync(cacheKey);
            if (string.IsNullOrWhiteSpace(cacheContent))
            {
                pagedList = await RepositoryWrapper.Author.GetAllAsync(parameters);
                var serializedContent = JsonConvert.SerializeObject(pagedList);
                await RedisHelper.SetAsync(cacheKey, serializedContent, TimeSpan.FromMinutes(1));
            }
            else
            {
                pagedList = JsonConvert.DeserializeObject<PagedList<Author>>(cacheContent);
            }

            var paginationMetedata = new
            {
                totalCount = pagedList.TotalCount,
                pageSize = pagedList.PageSize,
                currentPage = pagedList.CurrentPage,
                totalPages = pagedList.TotalPages,
                previousPageLink = pagedList.HasPrevious
                    ? Url.Link(nameof(GetAuthorsAsync), new
                    {
                        pageNumber = pagedList.CurrentPage - 1,
                        pageSize = pagedList.PageSize,
                        birthPlace = parameters.BirthPlace,
                        searchQuery = parameters.SearchQuery,
                        sortBy = parameters.SortBy
                    })
                    : null,
                nextPageLink = pagedList.HasNext
                    ? Url.Link(nameof(GetAuthorsAsync), new
                    {
                        pageNumber = pagedList.CurrentPage + 1,
                        pageSize = pagedList.PageSize,
                        birthPlace = parameters.BirthPlace,
                        searchQuery = parameters.SearchQuery,
                        sortBy = parameters.SortBy
                    })
                    : null
            };

            Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(paginationMetedata));
            var authorDtoList = Mapper.Map<IEnumerable<AuthorDto>>(pagedList);
            return authorDtoList.ToList();
        }

        [HttpGet("{authorId}", Name = nameof(GetAuthorAsync))]
        [ResponseCache(CacheProfileName = "Default", VaryByQueryKeys = new string[] { "authorId" })]
        public async Task<ActionResult<AuthorDto>> GetAuthorAsync(Guid authorId)
        {
            var author = await RepositoryWrapper.Author.GetAuthorAsync(authorId);
            if (author == null)
            {
                return NotFound();
            }

            var entityHash = HashFactory.GetHash(author);
            Response.Headers[HeaderNames.ETag] = entityHash;
            if (Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var requestETag) && entityHash == requestETag)
            {
                return StatusCode(StatusCodes.Status304NotModified);
            }

            var authorDto = Mapper.Map<AuthorDto>(author);
            return authorDto;
        }

        [HttpPost]
        public async Task<IActionResult> CreateAuthorAsync(AuthorForCreationDto authorForCreationDto)
        {
            var author = Mapper.Map<Author>(authorForCreationDto);

            RepositoryWrapper.Author.Create(author);
            var result = await RepositoryWrapper.Author.SaveAsync();
            if (!result)
            {
                throw new Exception("创建资源 author 失败");
            }

            var authorCreated = Mapper.Map<AuthorDto>(author);

            // 返回201 Created 状态码，并在响应消息头中包含 Location 项，它的值是新创建资源的 URL
            // 第一个参数是要调用 Action 的路由名称
            // 第二个参数是包含要调用 Action 所需要参数的匿名对象
            // 最后一个参数是代表添加成功后的资源本身
            return CreatedAtRoute(nameof(GetAuthorAsync), new { authorId = authorCreated.Id }, authorCreated);
        }

        [HttpDelete("{authorId}")]
        public async Task<ActionResult> DeleteAuthorAsync(Guid authorId)
        {
            var author = await RepositoryWrapper.Author.GetAuthorAsync(authorId);
            if (author == null)
            {
                return NotFound();
            }

            RepositoryWrapper.Author.Delete(author);
            var result = await RepositoryWrapper.Author.SaveAsync();
            if (!result)
            {
                throw new Exception("删除资源 author 失败");
            }

            return NoContent();
        }

        [HttpPatch("{authorId}")]
        [CheckIfMatchHeaderFilter]
        public async Task<IActionResult> PartiallyUpdateAuthorAsync(Guid authorId, JsonPatchDocument<AuthorForUpdateDto> patchDocument)
        {
            var author = await RepositoryWrapper.Author.GetAuthorAsync(authorId);
            if (author == null)
            {
                return NotFound();
            }

            // 资源已被修改，返回412
            var entityHash = HashFactory.GetHash(author);
            if (Request.Headers.TryGetValue(HeaderNames.IfMatch, out var requestETag) && requestETag != entityHash)
            {
                return StatusCode(StatusCodes.Status412PreconditionFailed);
            }

            var authorUpdateDto = Mapper.Map<AuthorForUpdateDto>(author);
            patchDocument.ApplyTo(authorUpdateDto, ModelState);
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            Mapper.Map(authorUpdateDto, author, typeof(AuthorForUpdateDto), typeof(Author));

            RepositoryWrapper.Author.Update(author);
            if (!await RepositoryWrapper.Author.SaveAsync())
            {
                throw new Exception("更新资源 Author 失败");
            }

            // 资源未被修改，更新散列值
            var entityNewHash = HashFactory.GetHash(author);
            Response.Headers[HeaderNames.ETag] = entityNewHash;

            return NoContent();
        }
    }
}
